package textconvert;

import java.io.IOException;
import java.io.InputStream;

/**
 * peek or unread: the count should be less than half of the buffer size.
 * @author Bing
 * @version 1.0
 *
 */
public class BufferedInputStream extends InputStream{
	private InputStream inStream;
	protected byte[] buffer = null;
	private int buffer_index = 0;
	private int buffer_size = 0;
	private int markpos;
	/**byte array object for reuse*/
	private ConstByteArray byteArray = null;
	private boolean byteArrayLocked = false;
	private boolean isInStreamEOF = false;

	public BufferedInputStream(InputStream inStream){
		this(inStream, 8192);
	}
	/**
	 * struct with buffer size, default 8192 (8K)
	 * @param inStream
	 * @param size
	 */
	public BufferedInputStream(InputStream inStream, int size){
		this.inStream = inStream;
		if(size % 2 != 0){
			size ++;
		}
		this.buffer = new byte[size];
		this.byteArray = new ConstByteArray(this.buffer, 0, size);
	}

	public int read() throws IOException{
		if(buffer_index>=buffer_size){           
			if(buffer_getNext()==-1)
				return -1;
		}
		return buffer[buffer_index++] & 0xff;   		
	}
	
	/**
	 * find a byte and lock the buffer to the byte
	 * @param b
	 * @param maxCount  should be less than half of buffer size
	 * @return null or byteArr
	 * @throws IOException
	 */
	public final ConstByteArray findBytePeekLock(byte b, int maxCount) throws IOException{
		byteBufferPeekLock(maxCount);
		byteBufferUnlock(0);
		int i;
		for(i=this.buffer_index; i<this.buffer_size; i++){
			if(buffer[i] == b){
				break;
			}
		}
		if(i == this.buffer_size)
			return null;
		return byteBufferPeekLock(i - this.buffer_index);
	}
	
	/**
	 * find a string and lock the buffer to the byte
	 * @param b
	 * @param maxCount  should be less than half of buffer size
	 * @return null or byteArr
	 * @throws IOException
	 */
	public final ConstByteArray findPeekLock(ConstByteArray b, int maxCount) throws IOException{
		byteBufferPeekLock(maxCount);
		byteBufferUnlock(0);
		int i;
		for(i=this.buffer_index; i<this.buffer_size; i++){
			if(b.regionMatches(this.buffer, i, b.length())){
				break;
			}
		}
		if(i == this.buffer_size)
			return null;

		return byteBufferPeekLock(i + b.length() - this.buffer_index);
	}
	
	/**
	 * find a string and lock the buffer to the byte
	 * @param b
	 * @return null or byteArr start with b
	 * @throws IOException
	 */
	public final ConstByteArray findLock(ConstByteArray b) throws IOException{
		int maxCount = this.buffer.length/2;
		
		for(;;){
			ConstByteArray lock = byteBufferPeekLock(maxCount);
			int count = lock.length() - b.length() + 1;		
			for(int i=0; i<count; i++){
				if(b.regionMatches(lock, i, b.length())){
					byteBufferUnlock(i);
					lock.resetSize(i, lock.length()-i-1);
					return lock;
				}
			}
			
			byteBufferUnlock(count);
			if(lock.length()<maxCount){				
				return null;
			}
		}				
	}
	
	/**
	 * find a string and lock the string buffer
	 * @param formatRule
	 * @return null or byteArr represent for formatRule
	 * @throws IOException
	 */
	public final ConstByteArray matchLock(FormatRule formatRule) throws IOException{
		FormatRule fr = formatRule;
		if(formatRule.getType()!=FormatRule.STATIC_TEXT)
			fr = formatRule.getNext();
		ConstByteArray b = findLock(fr.rule);
		byteBufferUnlock(0);
		
		if(b==null)
			return null;
		
		b = byteBufferPeekLock(this.getBufferSize()/2);
		ConstByteArray bb = new ConstByteArray(b);		
		int i = fr.length();
		int lastRuleLen = 0;
		for(FormatRule r=fr.getNext(); r!=null; r=r.getNext()){
			if(r.getType()!=FormatRule.STATIC_TEXT){
				i++;
				continue;
			}
			boolean found= false;
			for( ; i<bb.length(); i++){
				if(bb.regionMatches(r.rule, i, r.rule.length())){
					found = true;
					lastRuleLen = r.rule.length();
					break;
				}
			}
			if(!found){
				byteBufferUnlock(0);
				b = null;
				break;
			}
		}
		bb.resetSize(i + 1 + lastRuleLen);
		return bb;
	}
		
	
	/**
	 * peek bytes(size) and lock the whole buffer
	 * @param size
	 * @return ByteArray, and if its length < size, it means that it reaches the end of the stream.
	 * @throws IOException
	 */
	public final ConstByteArray byteBufferPeekLock(int size) throws IOException{
		if(size > this.buffer.length/2)
			throw new IOException("error:size > buffer.length/2");

		if(!this.isInStreamEOF ){
			if(buffer_index>=buffer_size)
				this.buffer_getNext();
			else if(buffer_size - buffer_index -1 <size)		
				this.moveBufferAndFill();
		}   
		int len = Math.min(size, buffer_size - buffer_index -1);
		this.byteArray.resetSize(this.buffer_index,len);
		byteArrayLocked = true;
		return this.byteArray;
	}
	
	/**
	 * unlock and skip bytes
	 * @param skipByteCount
	 * @throws IOException
	 */
	public final void byteBufferUnlock(int skipByteCount) throws IOException{
		byteArrayLocked = false;
		if(skipByteCount>0)
			this.skip(skipByteCount);
	}

	public void mark(int readlimit){
		if(readlimit>buffer.length/2)
			return;
		try {
			if(buffer_index>buffer.length/2 && readlimit > buffer_size - buffer_index - 1){               
				moveBufferAndFill();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		markpos = buffer_index;
	}

	public boolean markSupported() {
		return true;
	}

	public void reset(){
		buffer_index = markpos;
	}


	public void close(){
		buffer = null;
	}
	/**
	 * buffer size
	 * @return
	 */
	public final int getBufferSize(){
		return this.buffer.length;
	}

	/*
	 *
	 * @param bytes
	 * @param peekLength
	 * @return offset from the current position
	 */
//	public int matchBytesPeek(byte[] bytes, int peekLength) throws IOException{       
//		int halfCap = buffer.length /2;
//		if(bytes.length >  halfCap)
//			return -1;
//		if(buffer_index >= halfCap &&
//				buffer_size - buffer_index -1 < bytes.length){
//			if(moveBufferAndFill()==-1)
//				return -1;
//				}
//		int maxIndex = buffer_index + peekLength;
//		for(int i=buffer_index; i<maxIndex; i++){
//			int j;
//			for( j=0; j<bytes.length; j++){
//				if(buffer[i + j] != bytes[j]){
//					break;
//				}
//			}
//			if(j==bytes.length)
//				return i;
//		}
//		return -1;
//	}
	
	public void skip(int n) throws IOException{
		if(n <= buffer_size - buffer_index)   
			buffer_index += n;
		else{
			super.skip(n - (buffer_size-buffer_index));
			buffer_size = 0;
			buffer_index = 0;
		}
	}
	
	public boolean skipTo(ConstByteArray s) throws IOException{
		int len = s.length();
		if(len>=this.buffer.length/2)
			throw new IOException("too long to compare, cannot excced getBufferSize()/2");
		
		if(len==0)
			return true;
		
		ConstByteArray lockByteArr;
		for(;;){
			lockByteArr = byteBufferPeekLock(len);
			if(lockByteArr.length()<len){
				return false;
			}
			if(lockByteArr.equals(s)){
				return true;
			}
			byteBufferUnlock(1);
		}
	}	
	
	/**
	 * move buffer to previous buffer and read new to fill the rest
	 * @return the newly read bytes
	 * @throws IOException
	 */
	private int moveBufferAndFill() throws IOException{
		if(this.byteArrayLocked)
			throw new IOException("buffer locked!");
		int len = -1;
		if(buffer_index >= buffer.length /2){
			try {
				System.arraycopy(buffer, buffer_index, buffer, 0, buffer_size - buffer_index - 1);
				len = this.inStream.read(buffer, buffer_size, buffer.length-buffer_size);
				if(len!=-1)
					buffer_size += len;
				else
					this.isInStreamEOF = true;
				buffer_index = 0;               
			} catch (IOException e) {
				e.printStackTrace();
			}           
		}   
		return len;
	}
	/**
	 * clear buffer and read new bytes
	 * @return the newly read bytes count
	 * @throws IOException
	 */
	private int buffer_getNext() throws IOException{
		if(this.byteArrayLocked)
			throw new IOException("buffer locked!");
		int len = -1;
		try{
			len = this.inStream.read(buffer, 0, buffer.length);
			if(len != -1){
				buffer_size = len;
			}else{
				this.isInStreamEOF = true;
				buffer_size = 0;
			}
			buffer_index = 0;
		}catch(Exception e){
			e.printStackTrace();
		}
		return len;
	}

}

